package com.gitee.huanminabc.utils_common.obj.reflect;

import lombok.SneakyThrows;
import org.jsoup.helper.StringUtil;

import java.lang.reflect.Method;
import java.util.*;

public class ClassRuleMatching {




    /**
     * 根据规则找到对应的类
     * 规则:
     * 1. com.*.abc.ClassName 匹配所有com包下到abc包下的所有ClassName类
     * 2. com.*.abc.* 匹配所有com包下到abc包下的所有类
     * 3. com.*.abc.ClassName* 匹配所有com包下到abc包下的所有ClassName开头的类
     * 4. com.*.abc.*ClassName 匹配所有com包下到abc包下的所有类以ClassName结尾的类
     * 5. com.*.abc.*ClassName* 匹配所有com包下到abc包下的所有包含ClassName的类
     * 6. 完全匹配
     * 7. 多*匹配 com.*.dal.*.dao.*Dao 匹配所有com包下到dal包下的所有包,然后在匹配所有dao包下的所有Dao结尾的类
     * 8. com.*.dal.** 匹配所有com包下到dal包下的所有类
     * 9. * 匹配所有类
     */
    public static Set<String> matchingClassRule(String classRule, Set<String> classScans) {
        Set<String> result = new HashSet<>();

        //如果规则直接是*,那么就是匹配所有类
        if ("*".equals(classRule)) {
            return classScans;
        }

        //如果没有*号,那么就是完全匹配
        if (!classRule.contains("*")) {
            for (String classPath : classScans) {
                if (classPath.equals(classRule)) {
                    result.add(classPath);
                }
            }
            return result;
        }
        //遍历所有类,找到符合规则的类
        for (String classPath : classScans) {
            matchingClassRuleChild(result, classPath, classRule, classScans);
        }
        return result;
    }

    private static void matchingClassRuleChild(Set<String> result, String classPath, String classRule, Set<String> classScans) {

        //1.完全匹配
        if (classPath.equals(classRule)) {
            result.add(classPath);
            return ;
        }
        //如果没有*号,那么就是完全匹配,那么就不需要再匹配了
        if (!classRule.contains("*")) {
            return;
        }
        //截取rule文件名
        int i = classRule.lastIndexOf(".");
        if (i==-1){
            return;
        }
        String classRuleName = classRule.substring(i+1);
        //截取classPath文件名
        int i1 = classPath.lastIndexOf(".");
        if (i1==-1){
            return;
        }
        String classPathName = classPath.substring(i1+1);

        //0.如果classRuleName直接就是*,那么就表示匹配所有文件
        if (classRuleName.equals("*")) {
            //先去掉*号
            classRule = classRule.substring(0,i+1);
            //然后补充文件名
            classRule = classRule+classPathName;
            matchingClassRuleChild(result, classPath, classRule, classScans);
            return ;
        }

        //1.判断结尾是否是**
        if (classRule.endsWith("**")) {
            //截取**号前面的路径
            int i2 = classRule.indexOf("**");
            String prefixPath = classRule.substring(0, i2);
            if (classPath.startsWith(prefixPath) ) {
                //如果匹配就表示符合规则,将后面的路径替换
                classRule = classRule.substring(0,i2)+classPath.substring(i2);
                matchingClassRuleChild(result, classPath, classRule, classScans);
            }
            return ;
        }

        //2.匹配开头和结尾
        if (classRuleName.startsWith("*") && classRuleName.endsWith("*")) {
            //去掉开头和结尾的*
            String newClassRuleName = classRuleName.substring(1,classRuleName.length()-1);
            if(classPathName.contains(newClassRuleName)){
                //如果匹配就表示符合规则,将文件名替换
                classRule = classRule.substring(0,i+1)+classPathName;
                matchingClassRuleChild(result, classPath, classRule, classScans);
            }
            return ;
        }

        //3.文件名前缀匹配
        if (classRuleName.endsWith("*") ) {
            //去掉结尾的*
            String newClassRuleName = classRuleName.substring(0,classRuleName.length()-1);
            if(classPathName.startsWith(newClassRuleName)){
                //如果匹配就表示符合规则,将文件名替换
                classRule = classRule.substring(0,i+1)+classPathName;
                matchingClassRuleChild(result, classPath, classRule, classScans);
            }
            return ;
        }
        //4.文件名后缀匹配
        if (classRuleName.startsWith("*") ) {
            //去掉开头的*
            String newClassRuleName = classRuleName.substring(1);
            if(classPathName.endsWith(newClassRuleName)){
                //如果匹配就表示符合规则,将文件名替换
                classRule = classRule.substring(0,i+1)+classPathName;
                matchingClassRuleChild(result, classPath, classRule, classScans);
            }
            return ;
        }


        //5.判断路径中是否存在*
        if (classRule.contains("*")) {
            //截取*号前面的路径
            int i2 = classRule.indexOf("*");
            String prefixPath = classRule.substring(0, i2);
            //截取*号后面的路径
            String suffixPath = classRule.substring(i2+1);
            //截取最后一个*号后面的路径
            int i3 = suffixPath.lastIndexOf("*");
            String  lastSuffixPath = suffixPath.substring(i3+1);

            if (classPath.startsWith(prefixPath) && classPath.endsWith(lastSuffixPath)) {
                //截取中间的路径
                int i4 = classPath.indexOf(prefixPath);
                if (suffixPath.contains("*")){
                    int i5 = suffixPath.indexOf("*"); //截取*号前面的路径
                    String newSuffixPath = suffixPath.substring(0,i5);
                    int i6 = classPath.lastIndexOf(newSuffixPath);
                    if (i6==-1){
                        return;
                    }
                    String middlePath = classPath.substring(i4+prefixPath.length(),i6);
                    //将中间的路径补齐
                    classRule = classRule.substring(0,i2)+middlePath+classRule.substring(i2+1);
                    matchingClassRuleChild(result, classPath, classRule, classScans);
                }else{
                    int i5 = classPath.indexOf(suffixPath);
                    String middlePath = classPath.substring(i4+prefixPath.length(),i5);
                    //将中间的路径补齐
                    classRule = classRule.substring(0,i2)+middlePath+classRule.substring(i2+1);
                    matchingClassRuleChild(result, classPath, classRule, classScans);
                }
            }
        }
    }



    /**
     * 根据方法的全路径找到对应的类和方法(只找公共方法)
     * 规则:类的匹配规则和matchingClassRule一样,但是新增了方法的匹配规则
     * 1. methodName(type)//匹配指定参数类型的方法
     * 2. methodName(*) 匹配所有参数类型的方法
     * 3. *methodName() 匹配指定后缀的方法
     * 4. methodName*() 匹配指定前缀的方法
     * 5. *methodName*() 匹配包含指定的方法
     * 6. * 匹配所有方法
     * 注意: 方法类型必须是全路径比如,methodName(java.lang.String,type,type)
     */
    @SneakyThrows
    public static List<Method> matchingMethodRule(Set<String> classs, String methodRule) {
        List<Method> result = new ArrayList<>();

        //如果规则直接是*,那么就是匹配所有类的所有方法
        if ("*".equals(methodRule)) {
            for (String classPath : classs) {
                Method[] methods = Class.forName(classPath).getMethods();
                result.addAll(Arrays.asList(methods));
            }
            return  methodDeWeight(result);
        }


        //解析出方法类型数组
        Class[] methodTypes = getMethodTypes(methodRule);
        //去掉方法类型
        int i = methodRule.indexOf("(");
        methodRule = methodRule.substring(0, i);
        //如果没有*号,那么就是完全匹配
        if (!methodRule.contains("*")) {
            for (String classPath : classs) {
                //获取规则的方法
                if (methodTypes.length == 0) {
                    //如果不需要匹配参数类型,那么就直接获取方法
                    Method method = Class.forName(classPath).getMethod(methodRule);
                    result.add(method);
                } else if (methodTypes[0].equals(Void.class)) {
                    //如果是*号,那么就是匹配所有方法
                    Method[] methods = Class.forName(classPath).getMethods();
                    for (Method method : methods) {
                        //判断方法名称是否匹配
                        if (method.getName().equals(methodRule)) {
                            result.add(method);
                        }
                    }
                } else {
                    //按照参数类型匹配
                    Method method = Class.forName(classPath).getMethod(methodRule, methodTypes);
                    result.add(method);
                }

            }
            return  methodDeWeight(result);
        }
        for (String classPath : classs) {
            Class<?> aClass = Class.forName(classPath);
            //获取所有方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                matchingMethodRuleChild(result, method, methodRule, methodTypes);
            }
        }
        return  methodDeWeight(result);
    }

    private static void matchingMethodRuleChild( List<Method> result, Method method, String methodRule, Class[] methodRuleParams) throws ClassNotFoundException, NoSuchMethodException {
        //1.如果是*号,那么就是匹配所有方法
        if ("*".equals(methodRule)) {
            result.add(method);
            return;
        }
        //2.匹配开头和结尾
        if (methodRule.startsWith("*") && methodRule.endsWith("*")) {
            //去掉开头和结尾的*
            String newMethodRule = methodRule.substring(1, methodRule.length() - 1);
            if (method.getName().contains(newMethodRule)) {
                matchingMethodParams(result, method, methodRuleParams);
            }
            return;
        }
        //3.方法名称前缀匹配
        if (methodRule.endsWith("*")) {
            //去掉结尾的*
            String newMethodRule = methodRule.substring(0, methodRule.length() - 1);
            if (method.getName().startsWith(newMethodRule)) {
                matchingMethodParams(result, method, methodRuleParams);
            }
            return;
        }
        //4.方法名称后缀匹配
        if (methodRule.startsWith("*")) {
            //去掉开头的*
            String newMethodRule = methodRule.substring(1);
            if (method.getName().endsWith(newMethodRule)) {
                matchingMethodParams(result, method, methodRuleParams);
            }
            return;
        }
    }

    //匹配参数
    private static void matchingMethodParams( List<Method> result, Method method, Class[] methodRuleParams) {
        //判断参数是否匹配
        if (methodRuleParams.length == 0 && method.getParameterCount() == 0) {
            result.add(method);
        } else if (methodRuleParams[0].equals(Void.class)) {
            //如果是*号,那么就是匹配所有方法
            result.add(method);
        } else {
            Class<?>[] parameterTypes = method.getParameterTypes();
            //判断参数长度是否匹配
            if (parameterTypes.length != methodRuleParams.length) {
                return;
            }
            //判断参数类型是否匹配
            boolean flag = true;
            for (int i = 0; i < parameterTypes.length; i++) {
                if (!parameterTypes[i].equals(methodRuleParams[i])) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                result.add(method);
            }
        }
    }

    //解析出方法类型数组 methodName(type,type,type)
    public static Class[] getMethodTypes(String methodRule){
        //取出methodName(type,type,type)
        int i = methodRule.indexOf("(");
        int i1 = methodRule.indexOf(")");
        String types = methodRule.substring(i+1,i1);
        if (StringUtil.isBlank(types)){
            return new Class[0];
        }
        //如果是*号,那么就是匹配所有方法
        if (types.equals("*")) {
            return new Class[]{Void.class};
        }
        String[] split = types.split(",");
        Class[] classes = new Class[split.length];
        for (int i2 = 0; i2 < split.length; i2++) {
            try {
                classes[i2] = Class.forName(split[i2]);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return classes;
    }


    //同时匹配类和方法
    //com.gitee.huanminabc.utils_server.controller.**.mybatisAndMybatisPlus(*)
    //参数类型需要全路径
    public static List<Method> matchingClassAndMethodRule(Set<String> scanClass, String rule) {
        if (StringUtil.isBlank(rule)) {
            throw new RuntimeException("【AopCut->rule】切面规则不能为空");
        }
        if ("*".equals(rule)){
            //匹配类
            Set<String> matchingClassRule = ClassRuleMatching.matchingClassRule(rule, scanClass);
            //匹配方法
            return ClassRuleMatching.matchingMethodRule(matchingClassRule, rule);
        }
        //取出方法规则
        String methodRule = rule.substring(rule.indexOf("("));
        //取出类规则
        String classRule = rule.substring(0, rule.indexOf("("));

        //将最后一个.给methodRule
        int methodNameIndex = classRule.lastIndexOf(".");
        if (methodNameIndex != -1) {
            //截取方法名称
            String methodName = classRule.substring(methodNameIndex + 1);
            methodRule=methodName+methodRule;

            //将方法名称从类规则中删除
            classRule = classRule.substring(0, methodNameIndex);
        }

        //匹配类
        Set<String> matchingClassRule = ClassRuleMatching.matchingClassRule(classRule, scanClass);
        //匹配方法
        return ClassRuleMatching.matchingMethodRule(matchingClassRule, methodRule);
    }

    //去重
    @SneakyThrows
    public static List<Method> methodDeWeight(List<Method> methods) {
        List<Method> result = new ArrayList<>();
        Set<String> visited = new HashSet<>();
        for (Method method : methods) {
            Class declaringClass = method.getDeclaringClass();
            String name = method.getName();
            String[] array = Arrays.stream(method.getParameterTypes()).map(Class::getName).toArray(String[]::new);
            String key=declaringClass.getName() +"."+  name + Arrays.toString(array);
            if (!visited.contains(key)) {
                result.add(method);
                visited.add(key);
            }
        }
        visited.clear();
        return result;
    }

}
